spb/note-spboucher-ai
Public
1import { z } from "zod";2import { handler, parseBody } from "@/lib/api";3import { getDatabase, queryRows } from "@/lib/databases/service";4import { exportCsv } from "@/lib/databases/csv";5const rule = z.object({ property: z.string(), op: z.string(), value: z.unknown().optional() });6const group: z.ZodType<any> = z.lazy(() => z.object({ and: z.array(z.union([rule, group])).optional(), or: z.array(z.union([rule, group])).optional() }));7/** POST { viewId?, filter?, sorts?, search?, limit?, offset? } — explicit fields override the view's config. */8export const POST = handler(async ({ ws, params, req }) => {9 const b = await parseBody(req, z.object({ viewId: z.string().uuid().optional(), filter: group.nullable().optional(), sorts: z.array(z.object({ property: z.string(), dir: z.enum(["asc", "desc"]) })).optional(), search: z.string().max(200).optional(), limit: z.number().int().min(1).max(1000).optional(), offset: z.number().int().min(0).optional() }));10 const d = await getDatabase(ws.id, params.id); const v = d.views.find((x) => x.id === b.viewId);11 return Response.json(await queryRows(d.id, d.properties, { ...b, filter: b.filter === undefined ? (v?.config.filter ?? null) : b.filter, sorts: b.sorts ?? v?.config.sorts ?? [] }));12});13/** GET ?format=csv&viewId= */14export const GET = handler(async ({ ws, params, req }) => {15 const u = new URL(req.url); const d = await getDatabase(ws.id, params.id); const v = d.views.find((x) => x.id === u.searchParams.get("viewId"));16 const r = await queryRows(d.id, d.properties, { filter: v?.config.filter ?? null, sorts: v?.config.sorts ?? [], search: u.searchParams.get("q") ?? undefined, limit: 1000 });17 if (u.searchParams.get("format") === "csv") return new Response(exportCsv(d.properties, r.rows, v?.config.hidden ?? []), { headers: { "content-type": "text/csv; charset=utf-8", "content-disposition": `attachment; filename="${(v ? `${d.name}-${v.name}` : d.name).replace(/[^\w.-]+/g, "_")}.csv"` } });18 return Response.json(r);19});20